import Ft
from Ft.Xml import Parse
from Ft.Xml.XPath import Evaluate
from Ft.Xml.XPath.Context import Context
from Ft.Xml import EMPTY_NAMESPACE

from xml.dom import Node

filename = 'OpenDocument-schema-v1.0-os.rng'
uri = Ft.Lib.Uri.OsPathToUri(filename)
doc = Parse(uri)
# If the above XML document were located in the file
# "target.xml", we could have used `Parse("target.xml")`.
#print doc.xpath('string(ham//em[1])')
#Ft.Xml.Domlette.Print(doc)

defines = {}
elements = {}

namespaces = Ft.Xml.Domlette.GetAllNs(doc.rootNode)
namespaces.update({'hack':'http://relaxng.org/ns/structure/1.0',
                   'a'   : 'http://relaxng.org/ns/compatibility/annotations/1.0'})
context = Context(None, processorNss=namespaces)
define_nodes = Evaluate(u'.//hack:define', doc, context)
element_nodes = Evaluate(u'.//hack:element', doc, context)


def combineDefine(define):
   name = define.getAttributeNS(None, 'name')
   combine = define.getAttributeNS(None, 'combine')
   if not defines.has_key(name):
      cdefine = define.cloneNode()
      cdefine.removeAttributeNS(None, 'combine')
      doc = define.ownerDocument
      if 'choice' == combine:
         new_child = doc.createElementNS(namespaces['hack'], 'choice')
      else:
         assert 'interleave' == combine
         new_child = doc.createElementNS(namespaces['hack'], 'group')
      cdefine.appendChild(new_child)
      defines[name] = cdefine

   new_define = defines[name]
   assert 1 == len(new_define.childNodes)
   new_parent = new_define.firstChild
   define.normalize()
   for c in define.childNodes:
      if Node.TEXT_NODE == c.nodeType:
         continue
      cc = c.cloneNode(True)
      new_parent.appendChild(cc)

for dn in define_nodes:
   name = dn.getAttributeNS(None, 'name')
   if dn.hasAttributeNS(None, 'combine'):
      combineDefine(dn)
   else:
      assert not defines.has_key(name)
      defines[name] = dn

gNodeStack = []

class Attr(object):
   def __init__(self):
      self.name = name
      self.data_types = []
      self.values = []
      self.default = None

   def __str__(self):
      return "Attr %s t:%s v:%s d:%s" % (self.name, self.data_types, self.values, self.default)

   def trav(self, node):
      global gNodeStack
      if node in gNodeStack:
         print "ERROR: Recursion detected"
      gNodeStack.append(node)

      pre =  "-"*len(gNodeStack)
      do_trav = True
      assert 'element' != node.localName
      print "%s Attr::trav %s a:%s" % (pre, node.localName, self.name)

      if 'attribute' == node.localName:
         self.name = node.getAttributeNS(None, 'name').strip()
         if node.hasAttributeNS(namespaces['a'], 'defaultValue'):
            self.default = node.getAttributeNS(namespaces['a'], 'defaultValue')

      if 'ref' == node.localName:
         ref_name = node.getAttributeNS(None, 'name').strip()
         other_node = defines[ref_name]
         #print "ref [%s]: %s" % (ref_name, other_node)
         self.trav(other_node)
      #else:
      #print "%s %s e:%s a:%s" % (pre, node.localName, elm, attr)
      #ref_name = node.getAttributeNS(None, 'name').strip()

      if 'data' == node.localName:
         data_type = node.getAttributeNS(None, 'type').strip()
         self.data_types.append(data_type)

      if 'value' == node.localName:
         value = node.childNodes[0].data
         self.values.append(value)

      if do_trav:
         for n in node.childNodes:
            if Node.TEXT_NODE != n.nodeType:
               self.trav(n)

      assert gNodeStack[-1] == node
      gNodeStack = gNodeStack[:-1]

class Elem(object):
   def __init__(self):
      self.name = name
      self.data_types = []
      self.values = []
      self.attrs = {}
      self.childTypes = []

   def __str__(self):
      return "Elem %s t:%s v:%s c:%s" % (self.name, self.data_types, self.values, self.childTypes)

   def trav(self, node):
      global gNodeStack
      if node in gNodeStack:
         print "ERROR: Recursion detected"

      gNodeStack.append(node)

      pre =  "-"*len(gNodeStack)
      do_trav = True
      print "%s Elm::trav %s a:%s" % (pre, node.localName, self.name)

      if 'element' == node.localName:
         self.name = node.getAttributeNS(None, 'name').strip()

      for n in node.childNodes:
         if Node.TEXT_NODE != n.nodeType:
            self.trav_help(n)

      assert gNodeStack[-1] == node
      gNodeStack = gNodeStack[:-1]

   def trav_help(self, node):
      global gNodeStack
      if node in gNodeStack:
         print "ERROR: Recursion detected"

      if 'attribute' == node.localName:
         attr_name = node.getAttributeNS(None, 'name').strip()
         # XXX: Update an existing attribute if we already have it.
         attr = self.attrs.get(attr_name)
         if attr is None:
            attr = Attr()
         self.attrs[attr_name] = attr
         attr.trav(node)
         return

      if 'element' == node.localName:
         elm_name = node.getAttributeNS(None, 'name').strip()
         self.childTypes.append(elm_name)
         return

      gNodeStack.append(node)

      pre =  "-"*len(gNodeStack)
      do_trav = True
      print "%s Elm::trav %s a:%s" % (pre, node.localName, self.name)


      if 'ref' == node.localName:
         ref_name = node.getAttributeNS(None, 'name').strip()
         other_node = defines[ref_name]
         #print "ref [%s]: %s" % (ref_name, other_node)
         self.trav_help(other_node)

      if 'data' == node.localName:
         data_type = node.getAttributeNS(None, 'type').strip()
         self.data_types.append(data_type)

      if 'value' == node.localName:
         value = node.childNodes[0].data
         self.values.append(value)

      if do_trav:
         for n in node.childNodes:
            if Node.TEXT_NODE != n.nodeType:
               self.trav_help(n)

      assert gNodeStack[-1] == node
      gNodeStack = gNodeStack[:-1]


gNodeStack = []
for node in element_nodes:
   elm_name = node.getAttributeNS(None, 'name').strip()
   if elements.has_key(elm_name):
      print "Already have an elment '%s' using the latest version." % (elm_name)
   elm = Elem()
   elements[elm_name] = elm
   elm.trav(node)

print "Elements:"
for e in elements.values():
   print e
   for a in e.attrs.values():
      print "   %s" % (a)

# TODO:
#  - paragraph-content has a empty element that has a choice of names under it.
#  - form:list-value element is defined multiple times as an embedded element.
